The ValueStack ExplorerIntroductionOne of the most powerful parts of WebWork 2 and Struts 2 is the OGNL ValueStack, a stack which can be queried with OGNL expressions. Unfortuneatly enough, this very same part is the cause of much confusion amongst new users of the framework. There is a lot of information exposed to the view layer, but often it requires exploring the stack by a number of iterators. Testing expressions takes time, and sometimes a tiny typo can cause much grief. What is it ?The ValueStack Explorer is a combination of 2 tags (breakpoint and snapshot), a switch, and a monitoring client. The client allows you to send expressions to the breakpoint tag, which will block the current execution and send the response to the client. As such, it effectively operates as a breakpoint like any standard debugger. How does it work ?You start by starting the Switch - which is nothing more than a very simple message relay (=chat server). It accepts connections on a socket (by default it runs on port 4281). The client, created in Adobe Flash (should be ported to Java Swing - one day ..) connects to the Switch, can send OGNL statements and parses incoming (XML wrapped) responses. The breakpoint tag, when active, will open a socket and connect to the Switch as well, and execute any command against the OGNL Value Stack. InstallationDownload the first binary release here: http://wiki.opensymphony.com/download/attachments/8204/explorer-0.1.zip (source is coming, need to check Flash sources). Unzip the explorer-x.x.zip file. Place the explorer.jar file in your webapplication's WEB-INF/lib directory. Doubleclick it (or execute java -jar explorer.jar in a terminal) to start the switch. .. <param name="movie" value="explorer.swf?host=127.0.0.1&port=4281" /> .. <embed src="explorer.swf?host=127.0.0.1&port=4281" quality="high" bgcolor="#ffffff" width="1000" height="600" name="explorer" align="middle" allowScriptAccess="sameDomain" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" /> .. TutorialCreate a simple action called TestAction. .. public class TestAction extends ActionSupport { private List list; public String execute() throws Exception { list = new ArrayList(); list.add("Rene"); list.add("Peter"); list.add("Toby"); list.add("Rainer"); list.add("Patrick"); return super.execute(); } public List getList() { return list; } } Create a test page test.jsp which will display the list of names, and place it under the /WEB-INF/jsp/ directory. <%@ taglib uri="/webwork" prefix="ww" %> <%@ taglib uri="/vs" prefix="vs" %> <html> <head> <title>Test Page</title> <ww:head /> </head> <body> <vs:snapshot value="top.class" /> <ww:iterator value="list" status="status"> <li> <ww:property value="top"/> <vs:breakpoint active="true" name="personBP"/> </li> </ww:iterator> <vs:breakpoint active="true" name="finalBP" value="#context"/> <ww:iterator value="list" status="status"> <li><ww:property value="top"/></li> </ww:iterator> </body> </html> Register the action and the result in xwork.xml .. <action name="index" class="com.acme.test.TestAction"> <result name="success" type="dispatcher">/WEB-INF/jsp/test.jsp</result> </action> .. Start the Switch if you already haven't done so (see Installation above)- it should start running on port 4281. Visit the test action in your browser (preferably in another tab or window). It should render until you see about this:
It should keep loading (well, at least in FireFox). Now, when you check your Switch log you should see two connections - one from your Flash client, and one from the first breakpoint tag. If you can't see this, verify that the Switch is running, your Flash client is running (reconnect using the menu if necessary), and you're not dealing with a firewall or an already occupied port). Exploring the stackLet's examine the Flash client here for a moment. What you see is the result of a very first command sent by the breakpoint, to the Switch, which relayed it to the Flash client, which in turned transformed the XML into a tree. A full list will dropdown as soon as you click the arrow, and you can see every method, its return value and parameters. Now you can click every leaf in the three, and the tool will create the OGNL expression for you. If you click on "Rene", top will display in the input field below. Click on hashCode(), toString(), .. and it will result in top.hashCode() and top.toString(). When you're dealing with getters, Maps or Collections/Arrays, the client will provide you with the correct expression. Let's try issueing a custom command. Click on the input field below (besides the 'Query' button), and type #context, followed by a click on the Query button, or an <Enter>. A big list of objects will dropdown. These are all the objects which can be retrieved with the command #context. In fact, this is nothing more than a Map with a lot of Map.Entry objects in it. Once again, you can use the arrow to see what methods are available, but keep in mind: these are the methods for Map.Entry ! Once again, clicking on the object is enough to create the OGNL expression, so let's click "com.opensymphony.xwork.ActionContext.locale" [java.util.HashMap$Entry], and in the input field below, you'll get #context['com.opensymphony.xwork.ActionContext.locale']. Click the Query button again, and you'll receive the answer: "en_US" [java.util.Locale] (or some other Locale). When you expand the node now, you'll see it has all the methods for java.util.Locale instead of the old Map.Entry methods ! For example, clicking the getLanguage() method will result in #context['com.opensymphony.xwork.ActionContext.locale'].language, and submitting that will get you "en" as a response, on which you can invoke more String methods, and so on, ad infinitum. Now, this is nice, but there's more (of course, there's always more). There's a menu on top of the tree (Connection, Breakpoint, Stack, About), which we'll explore now. Connection is fairly obvious, it controls the connection to the Switch server, About just displays a mildly uninteresting popup, Breakpoint is something we'll talk about in a minute, which leaves us with .. Stack. Stack simply contains a list of some common commands, including #context (which we used above), #request, #session, etc .. clicking on them will simply send the command and show the result. Please note that not all commands return something (some are just empty - for example, #session will be empty until you actually place something in it (yes, this tool does not bend reality nor logic). BreakpointsNow, let's go to the next breakpoint. Remember, this is the very first breakpoint, and we have place a breakpoint inside a loop of a list with 5 names. Click the > arrow on the right of the Query button. Within an instant, you will be greated by "Peter". Clicking the > arrow again will show the next entry, "Toby". Take a look again at the test page in our browser, and you'll see it actually has advanced 2 items as well. More commandsOf course, you can do more than just simple object retrieval. How about testing ? You'll often find yourself using if/else structures, so we can easily test them as well. Let's try top eq "Jason", and just like you expected (I hope, if not, back away from the computer), "false" will be the result of this statement (with a nice icon - thanks Eclipse !). Similary, top eq "Patrick" will return "true", as will top.substring(0,2) == "Pa". Great ! Use the next button (>) to proceed to our next breakpoint. You'll see the breakpoint name has changed to finalBP (check the jsp page, you'll see we used that name on our last breakpoint, outside the loop - if you can't find this in your jsp, you're one lousy copy/paster, and you should exchange your PC for a Mac immediatly), and we can see the #context result is being displayed again. This is because we actually specified value="#context" in our tag (it defaults to 'top' if it's not specified). Sending top again will display the Action class. top.list.add("Claus"), press enter, and behold, a "true" tells us that we successfully added Claus to our list. A quick top.list indeed shows the name has been added. By the way, pressing the up key in the input field allows you to cycle through the used commands (a bit buggy atm though). SnapshotsThere is one last thing you might find interesting. There are snapshot tags that allow you to place anything on the stack (for comparison later on), which can be retrieved using #snapshots. Closing thoughtsSince this was the last breakpoint, pressing next will cause the page to be fully rendered. When you take a look at it, you'll see that we have in fact our initial 5 names, wich visual breakpoint marks, and a secondary listing with the additional 'Claus' name added to it. When you reload the page, you'll get "Rene" again, and so on .. Finally, I do realise that it's a bit awkward to use a Java daemon and a Flash client, and that we really should move to a single (preferably WebStart) Swing application instead. I just didn't have time for it (and I wanted to provide a quick popup in a webpage at first, so I guess one could say it's historically 'grown' ). Tag referenceBreakpoint Tag
Snapshot Tag
|